﻿// KeyTest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <windows.h>
#include <tchar.h>
#include <string>

#ifdef _UNICODE
using _tstring = std::wstring;
#else
using _tstring = std::string;
#endif

HHOOK g_hKbHook = NULL;
HHOOK g_hMouseHook = NULL;
bool g_fMouseShow = false;

VOID SetHook();
VOID DeleteHook();
LRESULT CALLBACK LowLevelKeybdProc(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam);
void LogPrintf(LPCTSTR pFormat, ...);

int main()
{
    auto hStdin = ::GetStdHandle(STD_INPUT_HANDLE);
    DWORD mode;
    ::GetConsoleMode(hStdin, &mode);
    mode &= ~ENABLE_QUICK_EDIT_MODE;
    ::SetConsoleMode(hStdin, mode);

    SetHook();

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }

    DeleteHook();
}

VOID SetHook()
{
    g_hKbHook =:: SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeybdProc, NULL, 0);
    g_hMouseHook = ::SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, NULL, 0);
}

VOID DeleteHook()
{
    if (g_hKbHook) {
        ::UnhookWindowsHookEx(g_hKbHook);
        g_hKbHook = NULL;
    }
    if (g_hMouseHook) {
        ::UnhookWindowsHookEx(g_hMouseHook);
        g_hMouseHook = NULL;
    }
}

void LogPrintf(LPCTSTR pFormat, ...)
{
    size_t nCchCount = MAX_PATH;
    _tstring strResult(nCchCount, 0);
    va_list args;

    va_start(args, pFormat);

    do
    {
        //格式化输出字符串
        int nSize = _vsntprintf_s(&strResult[0], nCchCount, _TRUNCATE, pFormat, args);
        if (-1 != nSize)
        {
            HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
            ::WriteConsole(console, strResult.c_str(), nSize, NULL, NULL);
            break;
        }

        //缓冲大小超限终止
        if (nCchCount >= INT32_MAX)
        {
            break;
        }

        //重新分配缓冲
        nCchCount *= 2;
        strResult.resize(nCchCount);

    } while (true);

    va_end(args);
}

void DebugKeyPrintf(WPARAM wParam, LPARAM lParam)
{
    static long count = 0;
    static KBDLLHOOKSTRUCT lastKey = { 0 };
    static long repeatCount = 0;
    ++count;
    KBDLLHOOKSTRUCT& event = *(PKBDLLHOOKSTRUCT)lParam;  // For convenience, maintainability, and possibly performance.
    bool keyDown = (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN);
    bool bShitf = GetAsyncKeyState(VK_SHIFT) & 0x8000;
    bool bCtrl = GetAsyncKeyState(VK_CONTROL) & 0x8000;
    bool bAlt = GetAsyncKeyState(VK_MENU) & 0x8000;
    bool bWin = GetAsyncKeyState(VK_LWIN) & 0x8000 || GetAsyncKeyState(VK_RWIN) & 0x8000;
    static DWORD dwLastTime = event.time;
    DWORD dwTime = event.time;

    static DWORD dwLastVk = 0;
    DWORD keyParam = event.scanCode << 16;

    typedef struct _KEY_FLAGS
    {
        DWORD Value;
        union
        {
            DWORD fExtended : 1;        // 指定键是扩展键，例如功能键还是数字键盘上的键。 如果键是扩展键，则值为 1;否则为 0
            DWORD fLLInjected : 1;      // 指指定事件是否是从在较低完整性级别运行的进程中注入的。 如果出现这种情况，则值为 1;否则为 0。 请注意，每当设置位 1 时，也会设置位 4。
            DWORD Reserved1 : 2;        // 保留
            DWORD fInjected : 1;        // 指定是否注入事件。 如果出现这种情况，则值为 1;否则为 0。 请注意，设置第 4 位时不一定设置位 1。
            DWORD fContext : 1;         // 上下文代码。 如果按下 Alt 键，则值为 1;否则为 0
            DWORD Reserved2 : 1;        // 保留
            DWORD fTransition : 1;      // 转换状态。 如果按下键，则值为 0;如果释放键，则值为 1
        };
    }KEY_FLAGS;

    KEY_FLAGS flags = { 0 };
    flags.Value = event.flags;

    //获取按键名称
    TCHAR szName[MAX_PATH] = { 0 };
    GetKeyNameText(keyParam, szName, _countof(szName));

    if (keyDown)
    {
        if (lastKey.vkCode == event.vkCode &&
            lastKey.scanCode == event.scanCode &&
            lastKey.flags == event.flags
            )
        {
            repeatCount++;
        }
        else
        {
            repeatCount = 0;
            lastKey = event;
        }

        if (0 == repeatCount)
        {
            dwLastTime = event.time;
        }

        dwTime = event.time - dwLastTime;

        LogPrintf(_T("Keyboard: %04u repeat:%03u vk:%02X(%03u) %-12s down Shift:%u Ctrl:%u Alt:%d Win:%u flags:%02X sc:%02X tm:%06d Ex:%04X\n"),
            count, repeatCount,
            event.vkCode, event.vkCode, szName, bShitf, bCtrl, bAlt, bWin, event.flags, event.scanCode, dwTime, (int)event.dwExtraInfo);
    }
    else
    {
        lastKey.vkCode = 0;
        lastKey.scanCode = 0;
        lastKey.flags = 0;
        repeatCount = 0;

        dwTime = event.time - dwLastTime;

        LogPrintf(_T("Keyboard: %04u repeat:%03u vk:%02X(%03u) %-12s   up Shift:%u Ctrl:%u Alt:%d Win:%u flags:%02X sc:%02X tm:%06d Ex:%04X\n"),
            count, repeatCount,
            event.vkCode, event.vkCode, szName, bShitf, bCtrl, bAlt, bWin, event.flags, event.scanCode, dwTime, (int)event.dwExtraInfo);
    }
}

void DebugMousePrintf(WPARAM wParam, LPARAM lParam)
{
    static long count = 0;
    LPMSLLHOOKSTRUCT lpMouseInfo = (LPMSLLHOOKSTRUCT)lParam;

    LPCTSTR lpMessageName[] = {
        _T("WM_MOUSEMOVE"),
        _T("WM_LBUTTONDOWN"),
        _T("WM_LBUTTONUP"),
        _T("WM_LBUTTONDBLCLK"),
        _T("WM_RBUTTONDOWN"),
        _T("WM_RBUTTONUP"),
        _T("WM_RBUTTONDBLCLK"),
        _T("WM_MBUTTONDOWN"),
        _T("WM_MBUTTONUP"),
        _T("WM_MBUTTONDBLCLK"),
        _T("WM_MOUSEWHEEL"),
        _T("WM_XBUTTONDOWN"),
        _T("WM_XBUTTONUP"),
        _T("WM_XBUTTONDBLCLK"),
        _T("WM_MOUSEHWHEEL"),
    };

    if (!g_fMouseShow)
    {
        return;
    }

    if (wParam > WM_MOUSEMOVE && wParam < WM_MOUSEWHEEL)
    {
        LogPrintf(_T("Mouse: %04u %04X Message: %-16s Pos %5d,%-5d Flag: 0x%.4X Time: %d ExtraInfo: 0x%p \n"),
            count,
            (DWORD)wParam,
            lpMessageName[wParam - WM_MOUSEMOVE],
            lpMouseInfo->pt.x,
            lpMouseInfo->pt.y,
            lpMouseInfo->flags,
            lpMouseInfo->time,
            (void*)lpMouseInfo->dwExtraInfo
        );
    }

    count++;
}

LRESULT CALLBACK LowLevelKeybdProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode >= 0)
    {
        KBDLLHOOKSTRUCT& event = *(PKBDLLHOOKSTRUCT)lParam;

        if (event.vkCode == VK_TAB && wParam == WM_KEYDOWN)
        {
                g_fMouseShow = !g_fMouseShow;
        }

        DebugKeyPrintf(wParam, lParam);
    }

    return CallNextHookEx(g_hKbHook, nCode, wParam, lParam);
}

LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode >= 0)
    {
        DebugMousePrintf(wParam, lParam);
    }

    return CallNextHookEx(g_hKbHook, nCode, wParam, lParam);
}